package proxy

import (
	"bytes"
	"crypto/tls"
	"gitee.com/gcom/ths_socket/codec"
	"gitee.com/gcom/ths_socket/socks5"
	"log"
	"net/http"
)

var HttpMethods = map[string]uint8{"GET": 1, "HEAD": 1, "POST": 1, "PUT": 1, "DELETE": 1, "CONNECT": 1, "OPTIONS": 1, "TRACE": 1}

var TlsVersions = map[int]string{
	tls.VersionSSL30: "ssl3.0",
	tls.VersionTLS10: "tls1.0",
	tls.VersionTLS11: "tls1.1",
	tls.VersionTLS12: "tls1.2",
	tls.VersionTLS13: "tls1.3",
}

var TlsVersionPrefix [][2]byte

func init() {
	TlsVersionPrefix = make([][2]byte, len(TlsVersions))
	for v, _ := range TlsVersions {
		TlsVersionPrefix = append(TlsVersionPrefix, [...]byte{byte(v >> 8 & 0xff), byte(v & 0xff)})
	}
}

var upTlsCache = tls.NewLRUClientSessionCache(500)
var ThsDelimiter = [...]byte{0xFD, 0xFD, 0xFD, 0xFD}

func ThsProxy(pipe *socks5.ConnPipe, ch chan<- error) {
	pipe.ReqCounter += 1
	buf, err := pipe.Downlink.Peek(10)
	if err != nil {
		ch <- err
		return
	}

	if buf[0] == byte(0x16) {
		for _, v := range TlsVersionPrefix {
			if bytes.Equal(v[:], buf[1:3]) {
				pipe.Protocol = "TLS"
				version := int(v[0]&0xff)<<8 + int(v[1]&0xff)
				log.Printf("[TLS:%d|%s]remote: %s, tlsVersion=%s,%d", pipe.ReqCounter, pipe.Protocol, pipe.DestAddr, TlsVersions[version], version)

				if pipe.DestAddr.FQDN == "" {
					pipe.DestAddr.FQDN, err = resolveHost(pipe.Downlink)
					if err != nil {
						log.Printf("TLS without hostname, fallback to TCP: %v", err)
						break
					}
					log.Printf("resolved handshake serverName: %s/%s", pipe.DestAddr.FQDN, pipe.DestAddr.IP)
				}
				cfg := createTLSConfig(pipe.DestAddr.FQDN, pipe.Config.Cert)
				downlink := tls.Server(pipe.Downlink, cfg)
				if err = downlink.Handshake(); err != nil {
					log.Printf("downlink handshake fail %s: %v", pipe.DestAddr, err)
					ch <- err
					return
				}
				pipe.Downlink = socks5.NewBufferedConnSize(downlink, 1024)

				uplink := tls.Client(pipe.Uplink, &tls.Config{ServerName: pipe.DestAddr.FQDN, ClientSessionCache: upTlsCache})
				if err = uplink.Handshake(); err != nil {
					log.Printf("uplink handshake fail %s: %v", pipe.DestAddr, err)
					ch <- err
					return
				}
				pipe.Uplink = uplink

				buf, err = pipe.Downlink.Peek(10)
				if err != nil {
					ch <- err
					return
				}
				break
			}
		}
	}

	var si = 0
	for i, b := range buf {
		if b == ' ' {
			si = i
			break
		}
		if b < 'A' || b > 'Z' {
			break
		}
	}
	if si > 0 {
		if pipe.Protocol == "TLS" {
			pipe.Protocol = "HTTPS"
		} else {
			pipe.Protocol = "HTTP"
		}
		m := string(buf[:si])
		if _, ok := HttpMethods[m]; ok {
			if pipe.ReqCounter == 1 {
				go socks5.ProxyIO(pipe.Downlink, pipe.Uplink, ch)
			}
		http:
			req, err := http.ReadRequest(pipe.Downlink.Buf)
			if err != nil {
				ch <- err
				return
			}
			log.Printf("[PROXY:%d|%s]host=%s, method=%s, url=%s", pipe.ReqCounter, pipe.Protocol, req.Host, req.Method, req.URL)
			req.Write(pipe.Uplink)
			pipe.ReqCounter += 1
			goto http
		}
	}

	if bytes.Equal(buf[:4], ThsDelimiter[:]) {
		if pipe.Protocol == "TLS" {
			pipe.Protocol = "THSs"
		} else {
			pipe.Protocol = "THS"
		}

		uplinkBuf := socks5.NewBufferedConn(pipe.Uplink)
		// receive from downstream
		go func() {
			downCounter := 0
		start1:
			p, e := codec.ReadClientPack(pipe.Downlink, 0)
			if e != nil {
				ch <- e
				log.Printf("DOWN:%s[%d]: read error: %s", pipe.Protocol, downCounter+1, pipe.DestAddr.IP)
				return
			}
			downCounter += 1
			if p == nil {
				log.Printf("DOWN:%s[%d]: heart beat: %s", pipe.Protocol, downCounter, pipe.DestAddr.IP)
				goto start1
			}
			log.Printf("DOWN:%s[%d]: MiniDataHead->%s: packLen=%d, headLen=%d, pkgId=%d, type=%X, pageId=%d, DataLen=%d, frameId=%d, textLen=%d",
				pipe.Protocol, downCounter, pipe.DestAddr.IP, len(p.OriginData), p.HeadLength, p.PackageId, p.HeadType, p.PageId, p.DataLength, p.FrameId, p.TextLength)
			uplinkBuf.Write(p.OriginData)
			goto start1
		}()

		// receive from upstream
		go func() {
			upCounter := 0
		start2:
			p, e := codec.ReadServerPack(uplinkBuf, 0)
			if e != nil {
				ch <- e
				log.Printf("UP:%s[%d]: read error: %s", pipe.Protocol, upCounter+1, pipe.DestAddr.IP)
				return
			}
			upCounter += 1
			if p == nil {
				log.Printf("UP:%s[%d]: heart beat: %s", pipe.Protocol, upCounter, pipe.DestAddr.IP)
				goto start2
			}
			log.Printf("UP:%s[%d]: MiniDataHead<-%s: model=%s, packLen=%d, headLen=%d, pkgId=%d, type=%X, PageId=%d, DataLen=%d, frameId=%d, textLen=%d",
				pipe.Protocol, upCounter, pipe.DestAddr.IP, p.Model, len(p.OriginData), p.HeadLength, p.PackageId, p.HeadType, p.PageId, p.DataLength, p.FrameId, p.TextLength)
			if p.Encrypt == codec.EncPlain {
				log.Printf("UP:%s[%d]: MiniDataHead<-%s: %s", pipe.Protocol, upCounter, pipe.DestAddr.IP, string(p.BinData))
			}
			pipe.Downlink.Write(p.OriginData)
			goto start2
		}()
		return
	}

	// TCP protocol
	if pipe.Protocol == "" {
		pipe.Protocol = "TCP"
	} else {
		pipe.Protocol = "TCPs"
	}
	log.Printf("[TCP] %s://%s", pipe.Protocol, pipe.DestAddr)
	go socks5.ProxyIO(pipe.Uplink, pipe.Downlink, ch)
	go socks5.ProxyIO(pipe.Downlink, pipe.Uplink, ch)
}
